using System;
using System.Linq;
using Kollektiv.Web.Mvc;
using Kollektiv.DataModel;
using System.Collections.Generic;

namespace Kollektiv.Controller
{
	[ControllerAttribute(PreActionHandler = "PreAction")]
	public class Session:Base
    {
        private static Dictionary<Guid, Queue<string>> sessionRequestQueues;
        private static Dictionary<Guid, Queue<Tuple<string, object>>> sessionResponseQueues;

        /// <summary>
        /// Adds a ui pong request.
        /// </summary>
        /// <param name="session">The session the request is targeted to.</param>
        /// <param name="request">The request string.</param>
        public static void AddSessionRequest(Kollektiv.DataModel.Session session, string request)
        {
            if (sessionRequestQueues == null)
                sessionRequestQueues = new Dictionary<Guid, Queue<string>>();

            if (!sessionRequestQueues.ContainsKey(session.UID))
                sessionRequestQueues.Add(session.UID, new Queue<string>());

            sessionRequestQueues[session.UID].Enqueue(request);
        }

        /// <summary>
        /// Adds a ui pong response.
        /// </summary>
        /// <param name="session">The session the response is targeted to.</param>
        /// <param name="response">The response string.</param>
        /// <param name="data">The response data.</param>
        public static void AddSessionResponse(Kollektiv.DataModel.Session session, string response, object data)
        {
            if (sessionResponseQueues == null)
                sessionResponseQueues = new Dictionary<Guid, Queue<Tuple<string, object>>>();

            if (!sessionResponseQueues.ContainsKey(session.UID))
                sessionResponseQueues.Add(session.UID, new Queue<Tuple<string, object>>());

            sessionResponseQueues[session.UID].Enqueue(new Tuple<string, object>(response, data));
        }

        /// <summary>
        /// Removes inactive sessions.
        /// </summary>
        private static void CleanUp()
        {
            SessionRepository sessionRepository = BaseDataService.GetRepository<SessionRepository>();

            List<Kollektiv.DataModel.Session> inactiveSessions = new List<Kollektiv.DataModel.Session>();
            foreach (Kollektiv.DataModel.Session session in sessionRepository.All.Where(s => s.LastActivityTime > DateTime.UtcNow.AddSeconds(SessionInactivityTimeout * -1)))
            {
                inactiveSessions.Add(session);
            }

            inactiveSessions.ForEach(s => sessionRepository.Delete(s));
        }

        /// <summary>
        /// Guest user.
        /// </summary>
        public const string _GUEST = "guest";

        /// <summary>
        /// Session route value.
        /// </summary>
		public const string _SESSION = "SESSION";
		
        /// <summary>
        /// Gets the kollektiv user session.
        /// </summary>
		public Kollektiv.DataModel.Session UserSession { get; private set; }
		
		private object PreAction ()
		{
			return this.PreActionParseSession ();
		}
		
        /// <summary>
        /// Checks the session on before the affected action gets executed.
        /// </summary>
        /// <returns></returns>
		private object PreActionParseSession ()
		{
            object retVal = null;

            string sessionUIDString = RouteData.Values[_SESSION] as string;
			Guid sessionUID = Guid.Empty;
			if (!string.IsNullOrEmpty (sessionUIDString)) {
				sessionUID = new Guid(sessionUIDString);
			}

            CleanUp();
				
			if (sessionUID != Guid.Empty) {
				this.UserSession = BaseDataService.GetRepository<SessionRepository> ().All
                    .Where (s => s.UID == sessionUID).FirstOrDefault ();
                this.UserSession.SetActivity();
			} else {
                UserRepository userRepository = BaseDataService.GetRepository<UserRepository> ();
			    SessionRepository sessionRepository = BaseDataService.GetRepository<SessionRepository> ();

                BaseDataService.BeginTransaction();
                try
                {
                    Kollektiv.DataModel.User user = userRepository.All.Where(u => u.Email == _GUEST).First();

                    this.UserSession = sessionRepository.Create(user);

                    BaseDataService.CommitTransaction();
                }
                catch (Exception ex)
                {
                    BaseDataService.RollbackTransaction();
                    throw (ex);
                }
                retVal = this.UserSession.UID;
			}

            return retVal;
		}

        /// <summary>
        /// Action handles client ping requests.
        /// </summary>
        /// <returns></returns>
        [ControllerAction]
        public Pong Ping()
        {
            Pong retVal = null;
            Tuple<string, object> response = null;
            string request = null;

            if ((response = GetNextResponse()) != null)
            {
                retVal = new Pong(PongType.Response, response.Item1, response.Item2);
            }
            else if (string.IsNullOrEmpty(request = GetNextRequest()))
            {
                retVal = new Pong(PongType.Request, request, null);
            }

            return retVal;
        }

        private string GetNextRequest()
        {
            string request = null;

            if(sessionRequestQueues.ContainsKey(this.UserSession.UID) && sessionRequestQueues[this.UserSession.UID].Count > 0)
            {
                lock(sessionResponseQueues[this.UserSession.UID])
                {
                    request = sessionRequestQueues[this.UserSession.UID].Dequeue();
                }
            }

            return request;
        }
        
        private Tuple<string, object> GetNextResponse()
        {
            Tuple<string, object> response = null;

            if(sessionResponseQueues.ContainsKey(this.UserSession.UID) && sessionResponseQueues[this.UserSession.UID].Count > 0)
            {
                lock(sessionResponseQueues[this.UserSession.UID])
                {
                    response = sessionResponseQueues[this.UserSession.UID].Dequeue();
                }
            }

            return response;
        }
	}
}

